
//
// 110521 - AH - (Opera) Created.
// 120117 - AH - (Huset) Counter property.
// 120220a- AH - (Aura) Renamed advanced() to painted().
// 120220b- AH - (Aura) Added ConversationEvent sequence property.
// 120224 - AH - (Kryddar *) Changes related to that the ScanPaintResult is
//               replaced by this: W, SW, LINES.
// 120315 - AH - (Aura) setCounter(int).
// 120326 - AH - (Huset) Renamed from ConversationElement.
// 120406 - AH - (Huset) Sets PAINT attribute on orb itself and getStamp().
// 120507 - AH - (Midtun) printConversationEvents().
// 120508a- AH - (Huset *) conversationEventsToString().
// 120508b- AH - (Huset *) log methods.
// 120518 - AH - (Aura) painted(Ontology source) now set 'semantic-effect'
//               attribute on what 'painted'.
// 120528 - AH - Moved passing strategy related code away from here.
//

package org.soekrat.engine.orb;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import org.soekrat.AttributeObject;
import org.soekrat.engine.event.SoekratChangeEvent;
import org.soekrat.engine.event.SoekratChangeListener;
import org.soekrat.engine.motor.ConversationEvent;
import org.soekrat.engine.visitor.Lines;
import org.soekrat.engine.wheel.ontology.Ontology;
import org.soekrat.environment.Orb;

/**
 * <p>
 *   {@link AttributeObject} in the use of being passed among objects involved
 *   in retrieval.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC)
 */
public final class Soekrat2Orb
{
  private final Orb m_orb;
  
  /**
   * <p>
   *   The name on the attribute for {@code this} for processed 'wheel'
   *   retrieved data to visitor.
   * </p>
   */
  public static final String W="w";
  
  /**
   * <p>
   *   The name on the attribute for {@code this} for processed 'spare wheel'
   *   retrieved data to visitor.
   * </p>
   */
  public static final String SW="sw";
  
  /**
   * <p>
   *   The name on the attribute for {@code this} for visitor input in form of
   *   {@link Lines} object.
   * </p>
   */
  public static final String CHAT_INPUT="lines";
  
  /**
   * <p>
   *   The name on the attribute for 
   *   {@link ConversationEvent}s for class on ontology that painted.
   * </p>
   */
  public static final String SOURCE_CLASS="source-class";
  
  /**
   * <p>
   *   The name on the attribute for 
   *   {@link ConversationEvent}s for name on ontology associated with event.
   * </p>
   */
  public static final String SOURCE_NAME="source";
  
  private transient List<SoekratChangeListener> m_listeners=null;
  
  private final List<ConversationEvent> m_events=
    new ArrayList<ConversationEvent>();
  
  private int m_counter=0;
  
  /**
   * <p>
   *   Constructor.
   * </p>
   */
  public Soekrat2Orb(Orb orb)
  { 
    if (orb==null)
    {
      throw new NullPointerException("orb");
    }
    
    m_orb=orb;
  }
    
  /**
   * <p>
   *   Advances counter.
   * </p>
   * @param source {@link Ontology} that painted.
   */
  public void painted(Ontology source)
  { 
    if (source==null)
    {
      throw new NullPointerException("source");  
    }
    
    m_counter++; 
    ConversationEvent e=createEvent("Painted");
    
    @SuppressWarnings("unchecked")
    List<AttributeObject> paint=(List<AttributeObject>)m_orb.getAttribute(
      Orb.PAINT);
    
    if (paint!=null) 
    {
      e.setAttribute(Orb.PAINT, paint);
      
      String semanticEffect=source.getSemanticEffect();
      for (AttributeObject curr : paint)
        curr.setAttribute("semantic-effect", semanticEffect);
    }
    
    m_orb.setAttribute(SOURCE_CLASS, source.getClass());
    
    e.setAttribute(SOURCE_NAME, source.getName());
    
    fire();
  }
  
  // UGLY, goes away when all done by passing strategy.
  public void setPainted(List<AttributeObject> wp, List<AttributeObject> swp)
  {
    m_orb.setAttribute(Soekrat2Orb.W, wp);
    m_orb.setAttribute(Soekrat2Orb.SW, swp);
    
    if (!swp.isEmpty())
    {
      ConversationEvent e=createEvent("Sparewheel Painted");
      e.setAttribute(Orb.PAINT, swp);
      fire();
    }
    
    // Eventually when all done by passing strategy this is how it will be one
    // list of what been painted.
    List<AttributeObject> painted=new ArrayList<AttributeObject>();
    for (AttributeObject curr : wp)
    {
      System.out.println("Adds from wheel: "+curr.getName());
      painted.add(curr);
    }
    for (AttributeObject curr : swp)
    {
      System.out.println("Adds from spare wheel: "+curr.getName());
      painted.add(curr);
    }
    
    m_orb.setAttribute(Orb.PAINT, painted);
  }
  
  /**
   * <p>
   *   Gets stamp retrieved has been stamped with.
   * </p>
   * @return Stamp or {@code null} if none found.
   */
  @SuppressWarnings("unchecked")
  public String getStamp()
  {
    List<AttributeObject> l=
      (List<AttributeObject>)m_orb.getAttribute(Orb.PAINT);
    String retVal=getStamp(l);
    if (retVal==null)
    {
      l=(List<AttributeObject>)m_orb.getAttribute(W);
      retVal=getStamp(l);
      if (retVal==null)
      {
        l=(List<AttributeObject>)m_orb.getAttribute(SW);
        retVal=getStamp(l);
      }
    }
    return retVal;
  }
  
  private static String getStamp(List<AttributeObject> l)
  {
    if (l==null) return null;
    for (AttributeObject curr : l)
    {
      String stamp=curr.getString("stamp");
      if (stamp!=null) return stamp;
    }
    return null;
  }
  
  /**
   * <p>
   *   Makes ready to be painted by result from new visitor input..
   * </p>
   * @param lines {@link Lines} which is visitor input.
   */
  public void turnTaking(Lines lines)
  {
    m_orb.clearAttributes();
    
    m_orb.setAttribute(CHAT_INPUT, lines);
    
    createEvent("Turn Taking");
    
    fire();
  }
  
  /**
   * <p>
   *   Gets counter value.
   * </p>
   * @return Value.
   */
  public int getCounter(){ return m_counter; }
  
  /**
   * <p>
   *   Sets counter.
   * </p>
   * <p>
   *   Should only be used by deserialization code; counter change else only
   *   by what 'happens' to the 
   *   {@code ConversationElement}.
   * </p>
   * @param c Value.
   * @throws IllegalArgumentException If {@code c<0}.
   */
  public void setCounter(int c)
  {
    if (c<0)
    {
      throw new IllegalArgumentException("c<0 : "+c);
    }
    
    m_counter=c;
  }
  
  /**
   * <p>
   *   Creates a
   *   {@link ConversationEvent} to be associated with {@code this} one's
   *   life.
   * </p>
   * @param desc Event description.
   * @return Created.
   */
  public ConversationEvent createEvent(String desc)
  {
    ConversationEvent retVal=new ConversationEvent(desc);
    m_events.add(retVal);
    return retVal;
  }
  
  /**
   * <p>
   *   Gets number of
   *   {@link ConversationEvent}s {@code this} have.
   * </p>
   * @return Count.
   */
  public int getEventCount(){ return m_events.size(); }
  
  /**
   * <p>
   *   Gets
   *   {@link ConversationEvent}.
   * </p>
   * @param idx Index of even to get.
   * @return {@code ConversationEvent}.
   * @throws IndexOutOfBoundsException If {@code idx<0 || idx>=getEventCount()}.
   */
  public ConversationEvent getConversationEvent(int idx)
  { 
    return m_events.get(idx); 
  }
  
  /**
   * <p>
   *   Adds 
   *   {@link SoekratChangeListener}.
   * </p>
   * @param l Listener to add.
   */
  public synchronized void addSoekratChangeListener(SoekratChangeListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_listeners==null) m_listeners=new ArrayList<SoekratChangeListener>();
    if (!m_listeners.contains(l)) m_listeners.add(l);
  }
  
  /**
   * <p>
   *   Removes. 
   *   {@link SoekratChangeListener}.
   * </p>
   * @param l Listener to remove.
   */
  public synchronized void removeSoekratChangeListener(SoekratChangeListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_listeners==null) return;
    m_listeners.remove(l);
  }
  
  private final SoekratChangeEvent m_event=new SoekratChangeEvent(this);
  
  // Fires change 
  private void fire()
  {
    if (m_listeners==null) return;
    
    SoekratChangeListener[] l=SoekratChangeListener.E_ARRAY;
    synchronized(this){ l=m_listeners.toArray(l); }
    
    for (SoekratChangeListener curr : l) curr.changed(m_event);
  }
  
  @Override
  public String toString() 
  {
    StringBuilder sb=new StringBuilder();
    sb.append("counter=").append(m_counter).append(", params=")
      .append(super.toString());
    return sb.toString();
  }
  
  /**
   * <p>
   *   Gets 
   *   {@code String} presentation of 
   *   {@link ConversationEvent}s.
   * </p>
   * @return {@code String} compiled.
   */
  public String conversationEventsToString()
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    for (ConversationEvent curr : m_events)
    {
      pw.println(curr);  
    }
    
    pw.flush();
    return caw.toString();
  }
  
  /**
   * <p>
   *   Prints
   *   {@link String} presentation of 
   *   {@link ConversationEvent}s to
   *   {@link System#out standard out}.
   * </p>
   */
  public void printConversationEvents()
  {
    System.out.println(conversationEventsToString());
  }
  
  // Used for internally logging.
  private Soekrat2Orb log(String method, String msg)
  { 
    return log(this, method, msg); 
  }
  
  public Object getAttribute(String name){ return m_orb.getAttribute(name); }
  
  public void setAttribute(String name, Object val)
  { 
    m_orb.setAttribute(name, val);
  }
  
  public void removeAttribute(String name){ m_orb.removeAttribute(name); }
  
  public Orb getRealOrb(){ return m_orb; }
  
  /**
   * <p>
   *   Logs.
   * </p>
   * <p>
   *   Messages will only be logged if
   *   {@link #log()}{@code ==true}.
   * </p>
   * @param source Source class (for static methods) or object logging. May be
   *               {@code null}.
   * @param method Name of method logging. May be {@code null}.
   * @param msg    Log message. Should not be {@code null} of course but is
   *               allowed to be.
   * @return {@code this}.
   */
  public Soekrat2Orb log(Object source, String method, String msg)
  {
    m_orb.log(source, method, msg);
    return this;
    /*
    if (!log()) return this;
    
    StringBuilder sb=new StringBuilder(">> ");
    
    String src=null;
    if (source!=null)
    {
      if (source instanceof Class<?>) src=((Class<?>)source).getSimpleName();
      else                            src=source.getClass().getSimpleName();
    }
    
    if (src!=null) sb.append(src).append(" : ");
    
    if (method!=null) sb.append(method).append(" : ");
    
    if (msg==null) sb.append("no message!");
    else           sb.append(msg);
    
    String compiled=sb.toString();
    
    System.out.println();
    System.out.println(compiled);
    System.out.println();
    
    return this;
    */
  }
  
}
